package com.hzit.user.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson2.JSON;
import com.hzit.common.constant.HzitCodeMessageTopicConstants;
import com.hzit.user.entity.User;
import com.hzit.user.exception.HandlerSentinelResourceMessage2;
import com.hzit.user.exception.HandlerSentinelResourceMessage2CommonException;
import com.hzit.user.mapper.UserMapper;
import com.hzit.user.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzit.user.vo.UserVo;
import com.ruoyi.common.core.utils.uuid.UUID;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.core.Local;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author wf
 * @since 2023-09-14
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private RocketMQTemplate mqTemplate;
	@Autowired
	private RedissonClient redissonClient;
	/**
	 * 1. 生成验证码并放到redis中
	 * @param phone
	 */
	@Override
	public void createCode(String phone) {
		//1.1 使用hutool工具生成一个随机的四位数字
		String s = RandomUtil.randomNumbers(4);

		//1.2 将生成的随机数字放到redis中
		redisTemplate.opsForValue().set(phone,s);
		//1.3 通过消息中间件发送消息，监听方收到消息就通过阿里云发送消息到你的手机上
		Map map = new HashMap<>();
		map.put("phone",phone);
		map.put("code",s);
		mqTemplate.convertAndSend(HzitCodeMessageTopicConstants.TOPIC,map);

		//发送事务消息示例
		// Message<?> message = MessageBuilder.withPayload(UserVo.builder().phone(phone).code(s)).build();
		// mqTemplate.sendMessageInTransaction("topic-01",message,null);
	}
	//2. 比较验证码是否成功
	@Override
	public boolean compareCode(String code, String phone) {
		//2.1 从redis中得到验证码
		String s = redisTemplate.opsForValue().get(phone);
		//2.2 与用户传入的验证码进行比较
		return s.equals(code);
	}

	//3. 保存用户
	@Override
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void save2() {
		String xid = RootContext.getXID();
		log.info("xid: {}",xid);
		User user = new User();
		user.setUsername("小张");
		user.setPassword("123");
		user.setCreated(LocalDateTime.now());
		user.setUpdated(LocalDateTime.now());
		int i = 10 / 0;
		this.save(user);
	}

	//2. 使用try...catch定义资源：方法一：
	@Override
	public String getMessage1() {
		// 1.5.0 版本开始可以利用 try-with-resources 特性（使用有限制）
		// 资源名可使用任意有业务语义的字符串，比如方法名、接口名或其它可唯一标识的字符串。
		try (Entry entry = SphU.entry("message1")) {
			// 被保护的业务逻辑
			// do something here...
			System.out.println("hello,message1...");
		} catch (BlockException ex) {
			// 资源访问阻止，被限流或被降级
			// 在此处进行相应的处理操作
			log.error("出现了限流:{}",ex);
		}
		return "message1";
	}

	/**
	 * 3. 定义资源方法二：使用@SentinelResource注解完成
	 * 3.1 blockHandler: 代表处理的异常方法在本类，必须与原来的签名一致，在参数列表后放一个BlockException异常！
	 * 3.2 blockHandlerClass：代表使用一个类处理异常，除了上面的要求外，方法还必须是static的,同时要添加blockHandler属性一起起作用。
	 * 3.3 fallback： 代表处理的是普通异常，方法要求与blockHandler一样。
	 * 3.4 fallbackClass: 代表处理的是普通异常，方法要求与blockHandlerClass是一致的。
	 */

	@Override
	// @SentinelResource(value = "message2",blockHandler = "handlerMessage2")
	// @SentinelResource(value = "message2",blockHandler = "handlerMessage2",blockHandlerClass = HandlerSentinelResourceMessage2.class)
	// @SentinelResource(value = "message2",fallback = "commonExceptionHandler")
	@SentinelResource(value = "message2",fallback = "commonExceptionHandler",fallbackClass = HandlerSentinelResourceMessage2CommonException.class)
	public String getMessage2() {
		int i = 10 / 0;
		return "message2";
	}

	@Override
	@SentinelResource(value="message3",fallback = "lianlou")
	public void getMessage3() {
		System.out.println("访问message3...");
	}

	//1. 分布式锁实现方式一：使用redis + Lua脚本实现
	//缺点：不能实现自动续期，到期时间了，如果业务没有完成也要释放哪把锁！
	@Override
	public List<User> findAll() {
		//1. 定义uuid，充当分布式锁
		String uuid = UUID.randomUUID().toString();
		String userListStr = redisTemplate.opsForValue().get("userList");
		//2. 获取到哪把锁(底层调用redis的setNX命令)
		Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
		List<User> users = null;
		//3. 如果获得了锁
		if(lock){
			try{
				// 3.1 处理自己的业务
				if(StrUtil.isNotBlank(userListStr)){    // 证明redis加有值
					users = JSON.parseArray(userListStr,User.class);
				}else{      // 从数据库中查询并放到redis中
					System.out.println("正在访问数据库。。。");
					users = this.list();
					redisTemplate.opsForValue().set("userList",JSON.toJSONString(users));
				}
			}finally {  //3.2 最终，处理完了自己的业务，释放哪把锁
				//3.2.1 定义要执行的lua脚本
				String s = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
				//3.2.2 定义redis脚本对象
				RedisScript<Long> scripts = new DefaultRedisScript<>(s);
				//3.2.3 释放锁，拿到执行结果
				Long result = redisTemplate.execute(scripts, Arrays.asList("lock"), uuid);
			}

		}else{  //3.3 进行自旋
			try {
				TimeUnit.MILLISECONDS.sleep(100);
				this.findAll();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return users;
	}

	//3. 实现分布式锁的方式二：使用redission框架实现
	//解决了redis + lua脚本的自动续期问题。
	@Override
	public List<User> findAll2() {
		List<User> users = null;
		//3.1 获取所加的哪把锁
		RLock lock = redissonClient.getLock("lock");
		lock.lock(20,TimeUnit.SECONDS);     // 此种写法没有用到看门狗机制
		//lock.lock();                                 // 此种写法使用了看门狗机制
		//3.2 执行自己的业务
		try{
			//3.2.1 获取用户列表
			String userListStr = redisTemplate.opsForValue().get("userList");
			//3.2.2 判断是否存在
			if(StrUtil.isNotBlank(userListStr)){    // 如果存在就转换为集合
				users = JSON.parseArray(userListStr,User.class);
				System.out.println("命中缓存。。。");
			}else{
				System.out.println("redission[正在从数据库查找]。。。");
				users = this.list();
				redisTemplate.opsForValue().set("userList",JSON.toJSONString(users));
			}

		}finally {  // 最后，释放哪把锁
			lock.unlock();
		}
		return users;
	}

	public void lianlou() {
		log.error("链路追踪出现异常...");
	}

	public String commonExceptionHandler(Throwable e) {
		log.error("资源message2出现的了限流异常[普通异常的处理],{}",e);
		return "message2";
	}
	//处理资源message2 出现的限流异常！，
	public String handlerMessage2(BlockException e) {
		log.error("资源message2出现的了限流异常！");
		return "handlerMessage2";
	}
	public static void main(String[] args) {
		String s = RandomUtil.randomNumbers(4);
		System.out.println("s = " + s);
	}
}
